/*
 * Copyright (c) winsoft666.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */
#ifndef VEIGAR_DETAIL_CALL_H_
#define VEIGAR_DETAIL_CALL_H_
#pragma once

#include <tuple>
#include "veigar/detail/func_tools.h"
#include "veigar/detail/invoke.h"

namespace veigar {
namespace detail {

//! \brief Calls a functor with argument provided directly
template <typename Functor, typename Arg>
auto call(Functor f, Arg&& arg) -> decltype(f(std::forward<Arg>(arg))) {
    return f(std::forward<Arg>(arg));
}

// Default behavior is to assume C++11, overriding VEIGAR_CXX_STANDARD can use newer standards:
#if VEIGAR_CXX_STANDARD >= 14

template <typename Functor, typename... Args, std::size_t... I>
decltype(auto) call_helper(Functor func, std::tuple<Args...>&& params, std::index_sequence<I...>) {
    return func(std::get<I>(params)...);
}

//! \brief Calls a functor with arguments provided as a tuple
template <typename Functor, typename... Args>
decltype(auto) call(Functor f, std::tuple<Args...>& args) {
    return call_helper(f,
                       std::forward<std::tuple<Args...>>(args),
                       std::index_sequence_for<Args...>{});
}

#else

// N is number of arguments left in tuple to unpack

template <size_t N>
struct call_helper {
    template <typename Functor, typename... ArgsT, typename... ArgsF>
    static auto call(Functor f, std::tuple<ArgsT...>& args_t, ArgsF&&... args_f)
        -> decltype(call_helper<N - 1>::call(
            f,
            args_t,
            std::get<N - 1>(args_t),
            std::forward<ArgsF>(args_f)...)) {
        return call_helper<N - 1>::call(
            f,
            args_t,
            std::get<N - 1>(args_t),
            std::forward<ArgsF>(args_f)...);
    }
};

template <>
struct call_helper<0> {
    template <typename Functor, typename... ArgsT, typename... ArgsF>
    static auto call(Functor f, std::tuple<ArgsT...>&, ArgsF&&... args_f)
        -> decltype(f(std::forward<ArgsF>(args_f)...)) {
        return f(std::forward<ArgsF>(args_f)...);
    }
};

//! \brief Calls a functor with arguments provided as a tuple
template <typename Functor, typename... ArgsT>
auto call(Functor f, std::tuple<ArgsT...>& args_t)
    -> decltype(call_helper<sizeof...(ArgsT)>::call(f, args_t)) {
    return call_helper<sizeof...(ArgsT)>::call(f, args_t);
}

#endif

}  // namespace detail
}  // namespace veigar

#endif  // !VEIGAR_DETAIL_CALL_H_
